Skip to content

mongo: partition snapshots for numeric and string _id collections#4487

Merged
jgao54 merged 6 commits into
PeerDB-io:mainfrom
andreyzhelnin-st:mongo-id-partitioning
Jul 2, 2026
Merged

mongo: partition snapshots for numeric and string _id collections#4487
jgao54 merged 6 commits into
PeerDB-io:mainfrom
andreyzhelnin-st:mongo-id-partitioning

Conversation

@andreyzhelnin-st

Copy link
Copy Markdown
Contributor

Summary

Extends the MongoDB connector so initial snapshots parallelize for numeric and string _id collections, not just ObjectID. Previously any non-ObjectID _id fell back to a single serial full-table partition, making large naturally-keyed collections (app-store numeric IDs, package-name strings) too slow to snapshot inside the oplog retention window.

Based on v0.36.29 (our deployed release).
It improves initial sync (MongoDb -> Clickhouse from 10 hours to 3 hours on 8mln collection).

Approach

GetQRepPartitions dispatches on the collection's min/max _id BSON type:

  • ObjectID — unchanged (uniform division of the 12-byte keyspace).
  • Int32/Int64 — uniform min/max division, reusing utils.PartitionHelper to emit non-overlapping IntPartitionRange partitions ($gte/$lte).
  • String — sampling-based quantile boundaries via $sample (honours read preference), anchored by the real min/max, emitted as a new StringPartitionRange proto type with contiguous half-open ranges ($gte/$lt).
  • Mixed-type / Double / empty — safe full-table fallback.

Changes

  • protos/flow.proto — add StringPartitionRange + string_range oneof field.
  • connectors/mongo/qrep_partition.go — type dispatcher; numeric + string builders; pure, unit-tested computeStringBoundaries.
  • connectors/mongo/qrep.goIntRange/StringRange cases in toRangeFilter.
  • connectors/utils/monitoring/monitoring.go — handle StringRange in addPartitionToQRepRun (avoids the unknown range type error path).

Testing

  • go build ./..., go vet clean.
  • New unit tests for computeStringBoundaries (even/clustered/sparse/duplicate/out-of-range/empty + contiguity), toRangeFilter (Int/String/ObjectID/unsupported), and _id type detection — all passing.

Notes / limitations

  • Numeric uses uniform value division; monitor partition balance on first run for sparse ID spaces.
  • Double _id and mixed-type collections fall back to full-table by design.
  • Requires regenerating protobuf bindings (./generate-protos.sh) before building images, as flow/generated is gitignored.

@CLAassistant

CLAassistant commented Jun 24, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Comment thread flow/connectors/clickhouse/cdc.go
@ilidemi ilidemi requested a review from jgao54 June 24, 2026 21:04
Comment thread flow/connectors/mongo/qrep_partition.go
Comment thread protos/flow.proto
Comment on lines +327 to +330
message StringPartitionRange {
string start = 1;
string end = 2;
}

@jgao54 jgao54 Jun 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are correctly working on StringPartitionRange for MySQL and decide to have StringPartitionRange introduced an end_inclusive boolean flag for String (see #4482), bringing in a small optimization where we don't have to reserve one of the partitions for the last row/document.

Comment on lines +237 to +246
case *protos.PartitionRange_IntRange:
// Numeric _id: inclusive range. PartitionHelper builds non-overlapping
// [start, end] integer ranges. Mongo compares int32/int64 numerically, so
// int64 bounds match int32-typed _ids.
return bson.D{
bson.E{Key: watermarkColumn, Value: bson.D{
bson.E{Key: "$gte", Value: r.IntRange.Start},
bson.E{Key: "$lte", Value: r.IntRange.End},
}},
}, nil

@jgao54 jgao54 Jun 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for mongo, using inclusive-inclusive int range [start, end] is prone to missing data in the rare scenario where user has doubles mixed in with integers. For example, if the _ids are 1, 2, 2.5, 3, 4; min/max will show up as 1 and 4, ended up with ranges [1, 2], [3, 4], we'd miss the 2.5 document.

we could introduce a NumericPartitionRange and use inclusive-exclusive [start, end), similar to StringPartitionRange, to avoid any gaps.

(While Mongo sorts by type so we don't have to worry about this for ObjectIDs, all numeric types are in the same type bucket)

@jgao54 jgao54 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution. Using sampling for string is a great strategy for MongoDB.

Could you also add e2e tests inflow/e2e/mongo_test.go, similar to Test_Simple_Flow_Partitioned, but for string and numeric _ids for full test coverage.

Comment on lines +186 to +189
if minVal >= maxVal {
c.logger.Info("[mongo] string min/max range is non-positive, falling back to full table partition")
return fullTablePartitions(), nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comparison uses golang instead of db collation, so is prone to false-positive. I think we can trust the db-fetched min/max using its ordering, and just check for equality here.

// boundaries are well distributed even with clustered keys.
stringSampleOversample = 20
// stringSampleMaxSize caps sampling cost on very large collections.
stringSampleMaxSize = 100000

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

today we limit numPartitions to 1000, so technically the max possible sample size is 20*1000=20000; we'll never hit this limit, but fine to keep as an upper bound

- Add end_inclusive to StringPartitionRange proto (aligns with MySQL PR
  PeerDB-io#4482 adding the same field; avoids future merge conflict)
- Replace sentinel-string approach with end_inclusive=true on the last
  string partition, using $lte instead of $lt for its MongoDB filter
- Sort string _id samples via MongoDB $sort stage instead of Go sort,
  preserving the collection's collation order
- Filter boundary samples by exact equality instead of <=/>= comparison,
  trusting MongoDB's min/max guarantees
- Add e2e tests for string and numeric _id partitioned snapshot flows
- Document the double _id gap limitation on numericPartitions (follow-up)
- Add utils.FullTablePartition() helper and use it in place of the local
  fullTablePartitions() in qrep_partition.go and the inline construction
  in qrep.go
- Remove slices.Compact() from computeStringBoundaries: the seen map
  already deduplicates interior values, and the quantile index formula
  is strictly increasing when len(interior) >= numPartitions, so no
  consecutive duplicates can occur in the else branch
- Tighten e2e partition count assertions to exact counts (10) instead
  of >1 for string and numeric _id tests
- Delete Test_Snapshot_Non_ObjectID_Falls_Back_To_Single_Partition:
  string _id collections are now partitioned rather than falling back
  to full-table, so the test expectation no longer holds
@andreyzhelnin-st

Copy link
Copy Markdown
Contributor Author

@jgao54 Addressed comments & rebased
Also go test -count=1 -v -timeout 30m -run 'TestMongoClickhouseSuite' ./e2e/ passes on my laptop.

@ilidemi ilidemi temporarily deployed to external-contributor July 2, 2026 02:38 — with GitHub Actions Inactive
@ilidemi ilidemi deployed to external-contributor July 2, 2026 02:38 — with GitHub Actions Active
@ilidemi ilidemi temporarily deployed to external-contributor July 2, 2026 02:38 — with GitHub Actions Inactive
@ilidemi ilidemi temporarily deployed to external-contributor July 2, 2026 02:38 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@jgao54 jgao54 merged commit 1e7a9a9 into PeerDB-io:main Jul 2, 2026
15 checks passed
jgao54 added a commit that referenced this pull request Jul 7, 2026
Follow-ups for MongoDB parallel snapshotting #4487 to introduce NumericPartitions; this avoids missing data during initial snapshots when there are interior fractional _ids.

A few other smaller clean-up, with inline comments.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants